// noinspection JSUnusedGlobalSymbols
/**
 * StorageUnit
 * @author LL
 * @date 2022/1/25 14:54
 */
export class StorageUnit {
    /* 字节 */
    private static readonly BYTE_VALUE = 1
    /* 千字节 */
    private static readonly KB_VALUE = StorageUnit.BYTE_VALUE << 10
    /* 百万字节 */
    private static readonly MB_VALUE = StorageUnit.KB_VALUE << 10
    /* G 字节 */
    private static readonly GB_VALUE = StorageUnit.MB_VALUE << 10
    /* T 字节 */
    private static readonly TB_VALUE = StorageUnit.GB_VALUE * 1024
    /* P 字节 */
    private static readonly PB_VALUE = StorageUnit.TB_VALUE * 1024

    /**
     * 静态实例: 字节
     * @type {StorageUnit}
     */
    public static readonly BYTE: StorageUnit = new StorageUnit(StorageUnit.BYTE_VALUE, 'Byte')
    /**
     * 静态实例: 千字节
     * @type {StorageUnit}
     */
    public static readonly KB: StorageUnit = new StorageUnit(StorageUnit.KB_VALUE, 'KB')
    /**
     * 静态实例: 兆字节
     * @type {StorageUnit}
     */
    public static readonly MB: StorageUnit = new StorageUnit(StorageUnit.MB_VALUE, 'MB')
    /**
     * 静态实例: GB
     * @type {StorageUnit}
     */
    public static readonly GB: StorageUnit = new StorageUnit(StorageUnit.GB_VALUE, 'GB')
    /**
     * 静态实例: TB
     * @type {StorageUnit}
     */
    public static readonly TB: StorageUnit = new StorageUnit(StorageUnit.TB_VALUE, 'TB')
    /**
     * 静态实例: PB
     * @type {StorageUnit}
     */
    public static readonly PB: StorageUnit = new StorageUnit(StorageUnit.PB_VALUE, 'PB')

    /**
     * 当前单位 一个单位的字节数
     * @type {number}
     * @private
     */
    private readonly value: number
    /**
     * 当前单位 视图名称
     * @type {string}
     * @private
     */
    private readonly name: string

    /* 构造, 一般使用静态常量即可 */
    constructor(value: number, name: string) {
        this.value = value
        this.name = name
    }

    /**
     * 转换到字节
     * 如果可能溢出, 按照JS正常规则溢出到负数
     * @param {number} size
     * @returns {number}
     */
    public toByte(size: number = 1) {
        return size * ( this.value )
    }

    /**
     * 转换到KB
     * 如果可能溢出, 按照JS正常规则溢出到负数
     * 如果从更小的单位转换 会丢失精度
     * @param {number} size
     */
    public toKByte(size: number) {
        return this.convert(size, StorageUnit.KB_VALUE, 10)
    }

    /**
     * 转换到MB
     * 如果可能溢出, 按照JS正常规则溢出到负数
     * 如果从更小的单位转换 会丢失精度
     * @param {number} size
     */
    public toMByte(size: number): number {
        return this.convert(size, StorageUnit.MB_VALUE, 20)
    }

    /**
     * 转换到GB
     * 如果可能溢出, 按照JS正常规则溢出到负数
     * 如果从更小的单位转换 会丢失精度
     * @param {number} size
     */
    public toGByte(size: number): number {
        return this.convert(size, StorageUnit.GB_VALUE, 30)
    }

    /**
     * 转换到TB
     * 如果可能溢出, 按照JS正常规则溢出到负数
     * 如果从更小的单位转换 会丢失精度
     * @param {number} size
     */
    public toTBType(size: number): number {
        return this.convert(size, StorageUnit.TB_VALUE, 40)
    }

    /**
     * @param size 数量
     * @param current 当前单位字节
     * @param {number} number 右移位
     */
    private convert(size: number, current: number, number: number): number {
        if (this.value > current) {
            return size * ( this.value >> number )
        }
        return ( size * this.value ) >> number
    }

    /* 实用工具 */

    /**
     * checkSize 小于 指定数量
     * @param {number} checkSize 待检查字节数
     * @param {number} unitSize 当前单位数量 默认 1
     * @returns {boolean} 小于
     */
    public lt(checkSize: number, unitSize: number = 1): boolean {
        return checkSize < this.toByte(unitSize)
    }

    public lte(checkSize: number, unitSize: number = 1): boolean {
        return checkSize <= this.toByte(unitSize)
    }

    /**
     * checkSize 大于指定数量
     * @param {number} checkSize 待检查字节数
     * @param {number} unitSize 当前单位数量 默认 1
     * @returns {boolean} 大于
     */
    public gt(checkSize: number, unitSize: number = 1): boolean {
        return checkSize > this.toByte(unitSize)
    }

    public gte(checkSize: number, unitSize: number = 1): boolean {
        return checkSize >= this.toByte(unitSize)
    }

    /**
     * 相等
     */
    public eq(checkSize: number, unitSize: number = 1): boolean {
        return checkSize == this.toByte(unitSize)
    }

    /**
     * 友好的显示, 类似 {@code ls -h}
     * @param {number} size 字节数
     * @param {number} decimal 小数位数, 默认两位
     * @return {string} 带有可读单位的描述 例: 123 KB
     */
    public static displayName(size: number, decimal: number = 2): string {
        let unit = StorageUnit.determineUnit(size)
        return `${ ( size / unit.value ).toFixed(decimal) } ${ unit.name }`
    }

    /**
     * 确定单位
     * @param {number} size 字节数
     * @return {StorageUnit} 合适的单位, 最大PB 永不为空
     */
    public static determineUnit(size: number): StorageUnit {
        if (size < StorageUnit.KB_VALUE) {
            return StorageUnit.BYTE
        }
        if (size < StorageUnit.MB_VALUE) {
            return StorageUnit.KB
        }
        if (size < StorageUnit.GB_VALUE) {
            return StorageUnit.MB
        }
        if (size < StorageUnit.TB_VALUE) {
            return StorageUnit.GB
        }
        if (size < StorageUnit.PB_VALUE) {
            return StorageUnit.TB
        }
        return StorageUnit.PB
    }

}
